package com.qunar.train.mock.dubbo;

import com.google.auto.service.AutoService;
import com.google.common.io.Closer;

import com.qunar.train.mock.util.AegisLog;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;

import static com.google.common.base.Charsets.UTF_8;
import static com.qunar.train.mock.dubbo.DaoMybatisConfigFileCreator.addNewPlugsInMybatisXML;

@AutoService(Processor.class)
public class GeneratorProcessor extends AbstractProcessor {

    private Messager messager;
    private Filer filer;
    private Elements elementsUtils;
    public Map<String, String> stringTypeClasses = new HashMap<>();
    public static Map<String, InterfaceType> inferfaceTypeMap = new HashMap<>();
    public static Map<String, List<String>> oldDubboSetMethodMap = new HashMap<>();
    private static String packageName = "";
    private String clsType = ""; //处理class类型，class or interface

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_7;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
        elementsUtils = processingEnv.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(AutoGenerator.class.getCanonicalName());
        return types;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        /**
         * 1.找到被注解类（MockCommom.java）,获取该类的package名称，拼接mock.mockDubbo,即为动态类的package路径
         * 2.查找并解析Spring-consummer配置文件，获取ID和接口名，并解析接口信息
         * 3.生成dubbo.xml配置文件
         * 4.根据接口信息生成String类信息
         * 5.动态生成类
         */

        generatePackageName(roundEnv);
        findOldDubboFile();
        parseDubboConfigFile(new GenerrateClassTool().findConfigFile("<dubbo:reference"));
        makeBeanFile();
        this.stringTypeClasses = generateDubboInterfaceStringImpl();
        generateDynamicSource();


        /**
         * 1.配置文件，在test.xml目录中添加been
         * 2. <bean id="aegisSpringUtil" class="com.qunar.train.mock.util.SpringUtil"/>
         * 3. <bean id="aegisQconfigProcessor" class="com.qunar.train.mock.config.AegisQconfigProcessor"/>
         */
        addNewBeansInTestXML();

        /**
         * 1.配置文件，在qunar-env.properties目录中添加name=beta
         */
        addNewBeansInEnvProperties();

        /**
         * 1.在个模块的test目录中添加mybatis配置文件，添加插件
         * 2. <plugins>
         *         <plugin interceptor="com.qunar.train.mock.database.MybatisSqlInterceptorImpl"/>
         *    </plugins>
         */
        addNewPlugsInMybatisXML(filer);
        return true;
    }

    private Map<String, List<String>> findOldDubboFile() {
        String path = this.getClass().getClassLoader().getResource("").getPath().split("/target")[0];
        File dictionary = new File(path + "/src/test/java/" + packageName.replace(".", "/"));
        log(dictionary.getPath());
        File[] files = dictionary.listFiles();
        if (files == null) return null;
        for (File file : files) {
            BufferedReader reader = null;
            List<String> strings = new ArrayList<>();
            try {
                reader = new BufferedReader(new FileReader(file));
                String tempString = null;
                int line = 1;
                // 一次读入一行，直到读入null为文件结束
                while ((tempString = reader.readLine()) != null) {
                    // 显示行号
//                    log("line " + line + ": " + tempString);
                    if (tempString.indexOf("public static void set") != -1) {
                        strings.add(tempString);
                    }
                    line++;
                }
                reader.close();
            } catch (IOException e) {
                log(e.toString());
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e1) {
                        log(e1.toString());
                    }
                }
            }
            String fileSimpleName = file.getName().substring(0, file.getName().lastIndexOf("."));
            log("fileSimepleName:" + fileSimpleName);
            log("strings:" + strings);
            oldDubboSetMethodMap.put(fileSimpleName, strings);
        }
        return oldDubboSetMethodMap;
    }

    private String generatePackageName(RoundEnvironment roundEnv) {
        Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(AutoGenerator.class);
        for (Element e : elememts) {
            //硬编码，在MockCommon上添加该标签，不然获取的包名路径可能有误
            if (ElementKind.CLASS == e.getKind()) {
                TypeElement typeElement = (TypeElement) e;
                this.packageName = elementsUtils.getPackageOf(typeElement).getQualifiedName().toString() + ".mock.mockDUBBO";// 获取包名并拼接为动态类的package路径
                log("pkgName:" + packageName); // 控制台输入的，没有实质意义
            }
        }
        return packageName;
    }

    private void parseDubboConfigFile(List<String> configRelativePaths) {

        if (configRelativePaths == null) return;
        for (String configRelativePath : configRelativePaths) {
            FileObject existingFile = null;
            try {
                existingFile = filer.getResource(StandardLocation.CLASS_PATH, "",
                        configRelativePath);
                log("Looking for existing resource file at " + existingFile.toUri());
                Map<String, InterfaceType> interfaceTypeMap = parseDubboConfigFileContent(existingFile.openInputStream());
                this.inferfaceTypeMap.putAll(interfaceTypeMap);
                for (Map.Entry<String, InterfaceType> entry : interfaceTypeMap.entrySet()) {
                    log("InterfaceTypeInfo:  " + entry.getValue().toString());
                }
            } catch (IOException e) {
                AegisLog.error(e);
            }
        }

    }

    /**
     * make file: dubbo.xml
     */
    private void makeBeanFile() {
        if (inferfaceTypeMap.isEmpty()) return;
        interfaceInfoUniqByclazz(inferfaceTypeMap);
        List<Object> interfaceInfokey = Arrays.asList(inferfaceTypeMap.keySet().toArray());
        if (interfaceInfokey == null) {
            log("interfaceInfokey is null!");
            return;
        }
        Collections.sort(interfaceInfokey, new GenerrateClassTool.SortByName());
        String beaninfo = "\n";
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < interfaceInfokey.size(); i++) {
            InterfaceType b = inferfaceTypeMap.get(interfaceInfokey.get(i));
            buf.append("\t<bean id=\"" + b.id + "\" class=\"" + b.implClazzName + "\"/>\n");
        }
        beaninfo = beaninfo + buf.toString();
        try {
            //maven-compile-plugin 执行google的注解@AutoService也会出现invalid relative name 的错误，原因是relativeName前面不能有斜杠，这是个坑
            log("generate spring-consumer.xml");
            FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", "aegis/dubbo/spring-consumer.xml", new Element[0]);
            log(fileObject.toUri().toString());
            OutputStream out = fileObject.openOutputStream();
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
            PrintWriter pw = new PrintWriter(writer, false);
            pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            StringBuilder beans = new StringBuilder();
            beans.append("<beans xmlns=\"http://www.springframework.org/schema/beans\"\n").
                    append("\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n").
                    append("\txmlns:context=\"http://www.springframework.org/schema/context\"\n").
                    append("\txmlns:tx=\"http://www.springframework.org/schema/tx\"\n").
                    append("\txmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:p=\"http://www.springframework.org/schema/p\"\n").
                    append("\txmlns:util=\"http://www.springframework.org/schema/util\"\n").
                    append("\txmlns:mvc=\"http://www.springframework.org/schema/mvc\"\n").
                    append("\txsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\n").
                    append("\thttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd\n").
                    append("\thttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd\n").
                    append("\thttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd\n").
                    append("\thttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd\"\n").
                    append("\tdefault-autowire=\"byName\">\n\n");
            pw.println(beans.toString());
            pw.println(beaninfo);
            pw.println("</beans>");
            pw.close();
            writer.flush();
            writer.close();
            out.close();
        } catch (IOException ex) {
            AegisLog.error(ex);
        }
        log("spring-consumer.xml has been generated");

    }

    private Map<String, String> generateDubboInterfaceStringImpl() {
        if (inferfaceTypeMap.isEmpty()) return null;
        Map<String, String> stringTypeClasses = new HashMap<>();
        try {
            Iterator<Map.Entry<String, InterfaceType>> entries = inferfaceTypeMap.entrySet().iterator();

            while (entries.hasNext()) {
                Map.Entry<String, InterfaceType> entry = entries.next();
                InterfaceType bean = entry.getValue();
                log(bean.toString());
                String stringClass = generateSingleInterfaceStringImpl(bean.interfaceName, bean.version, bean.implClazzName).toString();
                log(stringClass);
                stringTypeClasses.put(bean.implClazzName, stringClass);
                log(stringTypeClasses.keySet().iterator().next());
                log("generateDubboInterfaceImpl: id=" + bean.id + " ,class=" + bean.implClazzName + " ,version=" + bean.version);
            }
        } catch (Exception e) {
            AegisLog.error(e);
        }
        return stringTypeClasses;
    }

    private void generateDynamicSource() {
        if (inferfaceTypeMap.isEmpty()) return;
        for (Map.Entry<String, String> entry : stringTypeClasses.entrySet()) {
            Writer writer = null;
            try {
                log(entry.getKey());
                log(entry.getValue());
                JavaFileObject file = filer.createSourceFile(entry.getKey());
                writer = file.openWriter();
                writer.write(entry.getValue());
                writer.flush();

            } catch (IOException e) {
                AegisLog.error(e);
            } finally {
                try {
                    if (writer != null)
                        writer.close();
                } catch (IOException e) {
                    AegisLog.error(e);
                }
            }
        }
    }

    /**
     * Reads the spring consumer bean ids from the Spring-consumer file.
     *
     * @param input not {@code null}. Closed after use.
     * @return a not {@code null Set} bean ids.
     */
    List<String> readConsumerFile(InputStream input) throws IOException {
        List<String> list = GenerrateClassTool.readAndFilterCommit(input, " <dubbo:reference", "</dubbo:reference>");
        return list;
    }

    /**
     * interface info to IntefaceType Object
     */
    private static InterfaceType strTransToBean(String str) {
        InterfaceType interfaceType = new InterfaceType();
        try {
            interfaceType.id = str.split(" id")[1].split("\"")[1];
            interfaceType.interfaceName = str.split(" interface")[1].split("\"")[1];
            interfaceType.version = str.split(" version")[1].split("\"")[1];
        } catch (Exception e) {
            AegisLog.error(e);
        }
        return interfaceType;
    }

    Map<String, InterfaceType> parseDubboConfigFileContent(InputStream input) throws IOException {
        List<String> dubboComsumerList = readConsumerFile(input);
        Map<String, InterfaceType> interfaceTypeMap = new HashMap<>();
        for (String s : dubboComsumerList) {
            InterfaceType interfaceType = strTransToBean(s);
            interfaceType.implClazzName = packageName + ".Mock" + interfaceType.interfaceName.substring(interfaceType.interfaceName.lastIndexOf(".") + 1) + "$Impl";
            interfaceTypeMap.put(interfaceType.id, interfaceType);
        }
        return interfaceTypeMap;
    }

    /**
     * 解决类名相同包名不同问题，类名相同会生成一份mock类，存在覆盖 对包名不同类名相同的接口，Mock类命名采用id
     */
    private Map<String, InterfaceType> interfaceInfoUniqByclazz(Map<String, InterfaceType> map) {

        List<Map.Entry<String, InterfaceType>> list = new LinkedList<Map.Entry<String, InterfaceType>>(map.entrySet());
        int len = list.size();
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < len - 1; i++) {
            for (int j = i + 1; j < len; j++) {
                if (list.get(i).getValue().implClazzName.equals(list.get(j).getValue().implClazzName) &&
                        !list.get(i).getValue().interfaceName.equals(list.get(j).getValue().interfaceName)) {
                    log("存在simpleName相同，全类名不同的情况：" + list.get(i).getValue().interfaceName + "&&" + list.get(j).getValue().interfaceName);
                    set.add(i);
                    set.add(j);
                }
            }
        }

        Map<String, InterfaceType> result = new HashMap<String, InterfaceType>();
        if (set.size() == 0) {
            return result;
        }
        for (int n = 0; n < len; n++) {
            Map.Entry<String, GeneratorProcessor.InterfaceType> m = list.get(n);
            GeneratorProcessor.InterfaceType b = m.getValue();
            if (set.contains(n)) {
                b.implClazzName = b.implClazzName.substring(0, b.implClazzName.lastIndexOf(".")) + ".Mock" + b.id.substring(0, 1).toUpperCase() + b.id.substring(1) + "$Impl";
            }
            result.put(m.getKey(), b);
        }
        inferfaceTypeMap = result;
        for (Map.Entry<String, GeneratorProcessor.InterfaceType> entry : inferfaceTypeMap.entrySet()) {
            log(entry.getValue().toString());
        }
        return result;
    }


    /**
     * execute one interface-impl
     */
    private synchronized StringBuffer generateSingleInterfaceStringImpl(String clsName, String version, String mockName) {
        Class<?> cls = null;
        try {
            cls = Class.forName(clsName);
        } catch (ClassNotFoundException e) {
            AegisLog.error(e);
        }
        if (cls == null) return null;

        List<Method> methods = Arrays.asList(cls.getMethods());//not private
        if (methods == null) return null;

        Map<String, ClassFunction> funcInfo = new LinkedHashMap<String, ClassFunction>();
        ArrayList<String> memberToDo = new ArrayList<>();
        for (int i = 0; i < methods.size(); i++) {
            Method method = methods.get(i);
            ClassFunction func = GenerrateClassTool.parseMethod(method);

            if (func == null) continue;
            if (funcInfo.containsKey(func.memberName)) {
                memberToDo.add(func.memberName);
                func.memberName = GenerrateClassTool.renameFunc(method);
            }
            funcInfo.put(func.memberName, func);
        }
        if (memberToDo != null) {
            for (String key : memberToDo) {
                if (funcInfo.containsKey(key)) {
                    ClassFunction function = funcInfo.get(key);
                    funcInfo.remove(key);
                    function.memberName = GenerrateClassTool.renameFunc(function.method);
                    funcInfo.put(function.memberName, function);
                }
            }
        }
        StringBuffer s = makeImplStringTypeClass(cls, funcInfo, version, mockName);
        return s;
    }

    /**
     * make StringTypeClass: interfaceImpl or mockClass 支持泛型 dubbo所有的方法都是Spring注入的方式，不需要判断是否是libClass
     */
    private StringBuffer makeImplStringTypeClass(Class<?> clazz, Map<String, ClassFunction> funcInfo, String version, String mockName) {
        //修改为生成字符串的形式
        String clsName = clazz.getName();
        String interfaceName = clsName;
        if (mockName.equals("")) {
            mockName = "Mock" + clsName.substring(clsName.lastIndexOf('.') + 1);
        } else {
            mockName = mockName.substring(mockName.lastIndexOf(".") + 1);
        }

        StringBuilder meminfo = new StringBuilder("\n");
        StringBuilder instanceinfo = new StringBuilder("\n");
        StringBuilder funinfo = new StringBuilder("\n");
        StringBuilder setinfo = new StringBuilder("\n");
        StringBuilder clearinfo = new StringBuilder("\n");
        clearinfo.append("\tpublic static void clear() {\n");

        /** 用threadlocal代替instance，解决并发不稳定问题 */
        //meminfo.append("\tpublic static ").append(mockName).append(" instance = new ").append(mockName).append("();\n\n");
        meminfo.append("\tpublic static ThreadLocal<").append(mockName).append("> threadLocal = new ThreadLocal<>();\n");
        instanceinfo.append("\tpublic static ").append(mockName).append(" getInstance() {\n").
                append("\t\tif(threadLocal.get() == null){\n").
                append("\t\t\tthreadLocal.set(new ").append(mockName).append("());\n").
                append("\t\t}\n").
                append("\t\treturn threadLocal.get();\n").append("\t}\n\n");

        List<Object> funckey = Arrays.asList(funcInfo.keySet().toArray());
        Collections.sort(funckey, new GenerrateClassTool.SortByName());

        String modifyInfo = "";
        String returnInfoF = "";
        boolean hasT = false;
        for (int i = 0; i < funckey.size(); i++) {
            ClassFunction f = funcInfo.get(funckey.get(i));

            Pattern p = Pattern.compile("\\WT\\W");
            Matcher m1 = p.matcher(f.params);
            Matcher m2 = p.matcher(f.returns);
            if (m1.find() || m2.find()) {
                modifyInfo = " <T>";
                hasT = true;
            } else {
                modifyInfo = "";
            }

            //判断是否是static方法
            String funchead = "\tpublic";
            if (Modifier.toString(f.method.getModifiers()).indexOf("public static") > -1) {
                funchead = "\tpublic static";
            }

            if (f.returns.equals("void")) {
                funinfo.append(funchead).append(modifyInfo).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){}\n\n");
            } else {
                if (f.returns.equals("T")) {
                    returnInfoF = "return (T) instance.";
                    //modifyInfo = " <T>";
                    modifyInfo = GenerrateClassTool.typeFormat(f.method.getReturnType(), f.method.getGenericReturnType());
                } else {
                    returnInfoF = "return instance.";
                }
                funinfo.append(funchead).append(modifyInfo).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){\n\t\t").append(returnInfoF).append(f.memberName).append(";\n\t}\n\n");
                meminfo.append("\tprivate ").append(f.returns).append(" ").append(f.memberName).append(" = ").
                        append(f.memberDefault).append(";\n");
                setinfo.append("\tpublic static").append(modifyInfo).append(" void set").
                        append(f.memberName.substring(0, 1).toUpperCase() + f.memberName.substring(1)).
                        append("(").append(f.returns).append(" p){\n\t\tinstance.").append(f.memberName).append(" = p;\n\t}\n\n");
                clearinfo.append("\t\tinstance.").append(f.memberName).append(" = ").append(f.memberDefault).append(";\n");
            }
        }
        clearinfo.append("\t}\n\n");

        //添加旧dubbo中的set方法：
        String setTemInfo = setinfo.toString().replace("instance", "getInstance()");
        for (Map.Entry<String, List<String>> entry : oldDubboSetMethodMap.entrySet()) {
            if (mockName.contains(entry.getKey())) {
                for (String s : entry.getValue()) {
                    String temp = s.trim().substring(s.trim().indexOf("set"), s.trim().indexOf("("));
                    if (!setTemInfo.contains(temp)) {
                        setinfo.append("\n\t" + s + "}\n");
                        log(setinfo.toString());
                    }
                }
            }
        }

        //将类信息封装入Map中
        StringBuffer stringClass = new StringBuffer();
        stringClass.append("package " + packageName + ";\n\n");
        stringClass.append(GenerrateClassTool.getInfoImport(clsType));
        if (clazz.isInterface()) {
            stringClass.append("public class " + mockName + " implements " + interfaceName + " {\n");
        } else if (!GenerrateClassTool.isNeedExtends(clazz)) {
            stringClass.append("public class " + mockName + " {\n");
        } else {
            stringClass.append("public class " + mockName + " extends " + interfaceName + " {\n");
        }
        stringClass.append("\tpublic static String VERSION = \"" + version + "\";\n");
        stringClass.append(meminfo.toString());
        stringClass.append(instanceinfo.toString());
        stringClass.append(funinfo.toString().replace("instance", "getInstance()"));
        stringClass.append(setinfo.toString().replace("instance", "getInstance()"));
        stringClass.append(clearinfo.toString().replace("instance", "getInstance()"));
//        stringClass.append("public static void print(){\n" +
//                "        System.err.println(\"You are the greatest girl in the earth!\");\n" +
//                "    }");
        stringClass.append("}");
        return stringClass;
    }

    private void addNewBeansInTestXML() {
        try {
            log("find test.xml");

//            read File
            FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", "test.xml");
            log("Looking for existing resource file at " + existingFile.toUri());
            Closer closer = Closer.create();

            BufferedReader r = closer.register(new BufferedReader(new InputStreamReader(existingFile.openInputStream(), UTF_8)));
            String line;

            StringBuffer stringBuffer = new StringBuffer();
            while ((line = r.readLine()) != null) {
                stringBuffer.append(line + "\n");
            }
            log(stringBuffer.toString());
            String content = stringBuffer.toString().replace("</beans>", addNewBeanNames(stringBuffer.toString()));
            log("after change: \n" + content);
            r.close();
            closer.close();

//           write file
            GenerrateClassTool.createResource(filer, "test.xml", content);

        } catch (IOException ex) {
            AegisLog.error(ex);
        }
    }

    private void addNewBeansInEnvProperties() {
        try {
            log("find qunar-env.properties");

//            read File
            FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", "qunar-env.properties");
            log("Looking for existing resource file qunar-env.properties at " + existingFile.toUri());
            Closer closer = Closer.create();

            BufferedReader r = closer.register(new BufferedReader(new InputStreamReader(existingFile.openInputStream(), UTF_8)));
            String line;

            StringBuffer stringBuffer = new StringBuffer();
            while ((line = r.readLine()) != null) {
                stringBuffer.append(line + "\n");
            }
            log(stringBuffer.toString());
            String content = stringBuffer.append("name=beta").toString();
            log("after change: \n" + content);
            r.close();
            closer.close();

//           write file
            GenerrateClassTool.createResource(filer, "qunar-env.properties", content);

        } catch (IOException ex) {
            AegisLog.error(ex);
        }
    }

    private String addNewBeanNames(String beenString) {
        Map<String, String> beenMap = new HashMap<>();
        beenMap.put("aegisSpringUtil", "com.qunar.train.mock.util.SpringUtil");
        beenMap.put("aegisQconfigProcessor", "com.qunar.train.mock.config.AegisQconfigProcessor");
        StringBuilder beans = new StringBuilder();
        for (Map.Entry<String, String> entry : beenMap.entrySet()) {
            if (!beenString.contains(entry.getValue())) {
                beans.append("\t<bean id=\"" + entry.getKey() + "\" class=\"" + entry.getValue() + "\"/>\n");
            }
        }
        beans.append("</beans>");
        return beans.toString();
    }

    /**
     * inner data structure, with interface info
     */
    public static class InterfaceType {
        public String id;
        public String version;
        public String interfaceName;
        public String implClazzName;

        InterfaceType() {
            id = "";
            version = "";
            interfaceName = "";
            implClazzName = "";
        }

        InterfaceType(String id, String version, String interfaceName, String implClazzName) {
            this.id = id;
            this.version = version;
            this.interfaceName = interfaceName;
            this.implClazzName = implClazzName;
        }

        public String toString() {
            String content = "{\"id\":" + id + ",\"interfaceName\":" + interfaceName + ",\"version\":" + version + ",\"implClazzName\":" + implClazzName + "}";
            return content;
        }
    }

    /**
     * inner data structure, with method info
     */
    public static class ClassFunction {
        String name;
        String returns;
        String params;
        String memberName;
        String memberDefault;
        Method method;

        ClassFunction() {
            name = "";
            returns = "";
            params = "";
            memberName = "";
            memberDefault = "null";
            method = null;
        }

        public String toString() {
            String content = "{\"name\":" + name + ",\"returns\":" + returns + ",\"params\":" + params + ",\"memberName\":" + memberName + ",\"memberDefault\":" + memberDefault + ",\"method\":" + method + "}";
            return content;
        }
    }

    private void log(String msg) {
        messager.printMessage(Diagnostic.Kind.NOTE, msg);
        AegisLog.info(msg);
    }
}
